﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using FinPlusAssembler;
using FinPlusInterfaces;

namespace FinPlusCompProxy
{
    public class YieldCurve : ConcurrentDictionary<string, IRate>
    {
        private enum Params { LevelUpdate, Refresh, Shock, CurveName, Instrument, NumDepo, NumFut, NumFra, NumSwap, Index, DayCount, Tolerance, Holidays };
        private enum BuildType { DepositLoanBuild, FowardRateAgreementBuild, IRFutureBuild, VanillaSwapBuild, Unknown };

        private IList<IPod> _instruments;
        private QLAnalytics _analytics;
        private IPods _instrumentBuilds;
        private IPod _yieldCurveBuild;
        private BuildEvent _buildEvents;
        private int _numDepo, _numFut, _numFra, _numSwap, _depoCount, _fraCount, _futCount, _swapCount;
        private string _curveName, _index, _dayCount, _holidays;
        private double _tolerance;
        bool _hasBuilt;

        //construct
        public YieldCurve(QLAnalytics analytics, string curveName, IPod yieldCurveBuild, IPods instrumentBuilds, IList<IPod> instruments, BuildEvent buildEvents)
        {
            _analytics = analytics;
            _curveName = curveName;
            _yieldCurveBuild = yieldCurveBuild;
            _instrumentBuilds = instrumentBuilds;
            _instruments = instruments;
            _index = _yieldCurveBuild[Params.Index.ToString()].ToString();
            _dayCount = _yieldCurveBuild[Params.DayCount.ToString()].ToString();
            _tolerance = double.Parse(_yieldCurveBuild[Params.Tolerance.ToString()].ToString());
            _holidays = _yieldCurveBuild[Params.Holidays.ToString()].ToString();
            _numDepo = (int)_yieldCurveBuild.Value(Params.NumDepo.ToString());
            _numFut = (int)_yieldCurveBuild.Value(Params.NumFut.ToString());
            _numFra = (int)_yieldCurveBuild.Value(Params.NumFra.ToString());
            _numSwap = (int)_yieldCurveBuild.Value(Params.NumSwap.ToString());
            _buildEvents = buildEvents;
            Initialise();
        }

        public void Initialise()
        {
            foreach (var instrument in _instruments)
            {
                if (_buildEvents == BuildEvent.Refresh || _buildEvents == BuildEvent.UpdateAndRefresh)
                    instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                instrument.Get(Params.LevelUpdate.ToString(), IOC.New<IPod>(), Curve_Update);
                instrument.Get(Params.Shock.ToString(), IOC.New<IPod>(), Curve_Shock);

                var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                if (instrumentBuild != null)
                {
                    this[instrumentName] = AddRate(instrumentName, instrument, instrumentBuild);
                    instrument.Get(Params.Refresh.ToString(), string.Empty).Update(instrumentName);
                }
            }
        }

        //private
        private IRate AddRate(string instrumentName, IPod instrument, IPod instrumentBuild)
        {
            switch (instrumentBuild[PodField.Type.ToString()].ToString().EnumParse<BuildType>(BuildType.Unknown))
            {
                case BuildType.DepositLoanBuild: _depoCount++; return new DepoRate(_analytics, instrumentName, instrument, instrumentBuild);
                case BuildType.FowardRateAgreementBuild: _fraCount++; return new FraRate(_analytics, instrumentName, instrument, instrumentBuild);
                case BuildType.IRFutureBuild: _futCount++; return new FutRate(_analytics, instrumentName, instrument, instrumentBuild);
                case BuildType.VanillaSwapBuild: _swapCount++; return new SwapRate(_analytics, instrumentName, instrument, instrumentBuild);
                case BuildType.Unknown: break; //TODO 
            }

            return null;
        }

        private void BuildCurve()
        {
            try
            {
                string res;
                lock (_analytics.QL)
                {
                    res = _analytics.QL.YieldCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Rates(_analytics.BuySell), _dayCount, _tolerance, _holidays);
                    _hasBuilt = true;
                }
            }
            catch (Exception e)
            {
                Level.Error.Log(_yieldCurveBuild.ToString(), e);
            }
        }

        private string Rates(string buySell)
        {
            return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
        }

        private bool IsValid()
        {
            if (_numDepo != _depoCount || _numFut != _futCount || _numFra != _fraCount || _numSwap != _swapCount)
                return false;

            foreach (var rate in this.Values)
                if (Double.IsNaN(rate.Value))
                    return false;

            return true;
        }

        private void Curve_Update(object s, PropertyChangedEventArgs a)
        {
            var level = (IPod)((IBean)s).Object;
            var instrument = level[Params.Instrument.ToString()].ToString();

            if (this.ContainsKey(instrument))
            {
                IRate rate;
                if (this.TryGetValue(instrument, out rate))
                {
                    if (rate.Update(level) && IsValid() && (_buildEvents == BuildEvent.Update || _buildEvents == BuildEvent.UpdateAndRefresh || !_hasBuilt))
                        BuildCurve();
                }
            }
        }

        private void Curve_Refresh(object s, PropertyChangedEventArgs a)
        {
            if ((((IBean)s).ToString().Equals(_curveName)) && IsValid())
                BuildCurve();
        }

        private void Curve_Shock(object s, PropertyChangedEventArgs a)
        {
            var shock = (IPod)((IBean)s).Object;
            var curveName = shock[Params.CurveName.ToString()].ToString();
            if (curveName.Equals(_curveName))
            {
                var instrumentName = shock[Params.Instrument.ToString()].ToString();
                var value = (int)shock.Get(Params.Shock.ToString()).Object;
                this[instrumentName].Shock = value;
                if (value != 0 && IsValid()) BuildCurve();
            }
        }
    }

}
